/*
 * File:   socket.c
 * Author: onteria_
 *
 * Created on April 30, 2011, 7:11 AM
 */

#include <stdlib.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/time.h>

#include <miko/defines.h>
#include <miko/socket.h>

/**
* Function: miko_read_socket
 * 
 * Reads nbyte bytes of data from a file descriptor fd
 * into the buffer pointed to by buf. Shows error if the
 * read failed for any reason. Uses poll() to provide
 * connection timeout functionality.
 * 
 * Parameters:
 * 
 *      fd - File descriptor of the socket
 *      buf - The buffer to hold the value
 *      nbyte - The number of bytes to read
 *      timeout - The socket timeout value
 * 
 * Returns:
 *      
 *      The total bytes read equal to nbyte, or 
 *      POLLERROR if there was an error with poll
 *      TIMEOUT if the connection timed out
 */
extern long miko_read_socket(int fd, void * buf, size_t nbyte, int timeout) {
    fd_set set;
    struct timeval timeout_value;

    int select_result;
    long bytes_read;

    /* Initializing our data for select */
    FD_ZERO(&set);
    FD_SET(fd, &set);

    timeout_value.tv_sec = timeout;
    timeout_value.tv_usec = 0;

    select_result = select(FD_SETSIZE, &set, NULL, NULL, &timeout_value);
    if(select_result < 0) {
        return MIKO_POLLERROR;
    }
    else if(select_result == 0) {
        return MIKO_TIMEOUT;
    }
    else {    
        bytes_read = read(fd, buf, nbyte);
        if(bytes_read < 0) {
            perror("ERROR reading client socket");
        }

        return bytes_read;
    }
}

/**
 * Function: miko_create_socket6
 * 
 * Create an IPV6 socket from a given port and address.
 * Uses getaddrinfo since it's IPV6 friendly.
 * 
 * Parameters:
 * 
 *      port - The port to use
 *      address - The address of the host
 * 
 * Returns:
 *      
 *      An IPV6 socket or NULL if there was an error
 */
extern struct sockaddr_in6 miko_create_socket6(int port, const char * address) {

    struct addrinfo hints, *res;
    struct sockaddr_in6 input_socket6;
    int errcode;

    /* 0 out our structs to be on the safe side */
    memset (&hints, 0, sizeof (hints));
    memset (&input_socket6, 0, sizeof(struct sockaddr_in6));

    /* We only care about IPV6 results */
    hints.ai_family = AF_INET6;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_DEFAULT;
    
    errcode = getaddrinfo (address, NULL, &hints, &res);
    if (errcode != 0)
    {
      perror ("[ERROR] getaddrinfo ");
      return input_socket6;
    }

    while (res)
    {
    	/* Check to make sure we have a valid AF_INET6 address */
        if(res->ai_family == AF_INET6) {
        	/* Use memcpy since we're going to free the res variable later */
			memcpy (&input_socket6, res->ai_addr, res->ai_addrlen);

			/* Here we convert the port to network byte order */
			input_socket6.sin6_port = htons (port);
			input_socket6.sin6_family = AF_INET6;
            break;
        }

        res = res->ai_next;
    }

    freeaddrinfo(res);

    return input_socket6;
}

/**
 * Function: miko_create_socket
 * 
 * Create an IPV4 socket from a given port and address.
 * Uses getaddrinfo since it's IPV6 friendly.
 * 
 * Parameters:
 * 
 *      port - The port to use
 *      address - The address of the host
 * 
 * Returns:
 *      
 *      An IPV4 socket or NULL if there was an error
 */
extern struct sockaddr_in miko_create_socket(int port, const char * address) {

    struct addrinfo hints, *res;
    int errcode;
    
    struct sockaddr_in input_socket;

    memset (&hints, 0, sizeof (hints));
    memset (&input_socket, 0, sizeof(struct sockaddr_in));

    /* Force IPV4 only returns */
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_DEFAULT;

    errcode = getaddrinfo (address, NULL, &hints, &res);
    if (errcode != 0)
    {
      perror ("[ERROR] getaddrinfo ");
      return input_socket;
    }

    /* Loop through the results, skipping any IPV6 addresses */
    while (res)
    {
        if(res->ai_family == AF_INET) {
        	/* Use memcpy since we're going to free the res variable later */
			memcpy (&input_socket, res->ai_addr, res->ai_addrlen);

			/* Here we convert the port to network byte order */
			input_socket.sin_port = htons(port);
			input_socket.sin_family = AF_INET;
            break;
        }

        res = res->ai_next;
    }
    
    freeaddrinfo(res);

    return input_socket;
}
